﻿namespace Hopfield.Simulator
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Drawing;
    using System.Data;
    using System.Globalization;
    using System.Text;
    using System.Threading;
    using System.Windows.Forms;

    [HopfieldSimulatorPage("Pictures")]
    public partial class HopfieldPictures : UserControl, IHopfieldSimulatorPage
    {
        #region Fields

        private static object Mutex = new object();
        private readonly ToolStripStatusLabel info;
        private readonly ToolStripStatusLabel infoMSE;
        private readonly Form parentForm;
        private int noise = 35;

        #endregion

        #region Constructor

        public HopfieldPictures(Hopfield.Math.Hopfield hopfield,
                          ToolStripStatusLabel info,
                          ToolStripStatusLabel infoMSE,
                          Form parentForm)
        {
            InitializeComponent();

            // Dependency injection from parent.
            this.Hopfield = hopfield;
            this.info = info;
            this.infoMSE = infoMSE;
            this.parentForm = parentForm;

            // Set d
            int d = 128;
            this.Hopfield.Setd(d);
            this.textBoxD.Text = d.ToString(CultureInfo.InvariantCulture);

            // Set eta
            this.Hopfield.Seteta(0.7f);
            this.textBoxEta.Text = (0.7f).ToString(CultureInfo.InvariantCulture);

            // Set MSE
            this.Hopfield.SetMSEmin(0.3f);
        }

        #endregion

        #region Finalizer

        ~HopfieldPictures()
        {
        }

        #endregion

        #region Properties

        Hopfield.Math.Hopfield hopfield = null;

        public Hopfield.Math.Hopfield Hopfield
        {
            get { return this.hopfield; }
            private set { this.hopfield = value; }
        }

        #endregion

        #region Methods

        public void Activate()
        {
            int d = 0;
            if (true == int.TryParse(textBoxD.Text, NumberStyles.Integer, CultureInfo.InvariantCulture, out d))
            {
                this.Hopfield.Setd(d);

                if (this.ChangedD != null)
                {
                    this.ChangedD(this, EventArgs.Empty);
                }
            }

            float eta = 0.7f;
            if (true == float.TryParse(textBoxEta.Text, NumberStyles.Float, CultureInfo.InvariantCulture, out eta))
            {
                this.Hopfield.Seteta(eta);

                if (this.ChangedEta != null)
                {
                    this.ChangedEta(this, EventArgs.Empty);
                }
            }

            if (mainSimulatorPictureBox.Image != null)
            {
                this.Hopfield.Recreate(this.Hopfield.Rows * this.Hopfield.Cols);
            }
        }

        private void OpenHNFile()
        {
            this.Invoke(new MethodInvoker(() =>
            {
                this.info.Text = "Opening...";
            }));

            string fileName = string.Empty;

            this.Invoke(new MethodInvoker(() =>
            {
                using (OpenFileDialog ofd = new OpenFileDialog())
                {
                    ofd.Filter = "Hopfield Net (*.hn)|*.hn";
                    if (ofd.ShowDialog() != DialogResult.OK)
                    {
                        return;
                    }

                    fileName = ofd.FileName;
                }
            }));

            if (true == string.IsNullOrEmpty(fileName))
            {
                this.info.Text = "Ready.";
                return;
            }

            this.Hopfield.OpenHNFile(fileName);

            this.Invoke(new MethodInvoker(() =>
            {
                mainAttractorsListBox.Items.Clear();
                int c = Hopfield.LCount;
                for (int i = 0; i < c; i++)
                {
                    this.mainAttractorsListBox.Items.Add(i.ToString(CultureInfo.InvariantCulture));
                }

                this.checkBoxColors.Checked = this.Hopfield.Colorized;
                this.checkBoxColors.Enabled = this.mainAttractorsListBox.Items.Count == 0;

                this.info.Text = "Ready.";
            }
            ));
        }

        private void ThreadOpenHNFile()
        {
            lock (Mutex)
            {
                this.OpenHNFile();
            }
        }

        public void FileOpen()
        {
            Thread th = new Thread(new ThreadStart(this.ThreadOpenHNFile));
            th.Start();
        }

        private void SaveHNFile()
        {
            this.Invoke(new MethodInvoker(() =>
            {
                this.info.Text = "Saving...";
            }));

            string fileName = string.Empty;

            this.Invoke(new MethodInvoker(() =>
            {
                using (SaveFileDialog sfd = new SaveFileDialog())
                {
                    sfd.Filter = "Hopfield Net (*.hn)|*.hn";
                    if (sfd.ShowDialog() != DialogResult.OK)
                    {
                        return;
                    }

                    fileName = sfd.FileName;
                }

                if (true == string.IsNullOrEmpty(fileName))
                {
                    this.info.Text = "Ready.";
                    return;
                }
            }));

            this.Hopfield.SaveHNFile(fileName);

            this.Invoke(new MethodInvoker(() =>
            {
                this.info.Text = "Ready.";
            }));
        }

        private void ThreadSaveHNFile()
        {
            lock (Mutex)
            {
                this.SaveHNFile();
            }
        }

        public void FileSave()
        {
            Thread th = new Thread(new ThreadStart(this.ThreadSaveHNFile));
            th.Start();
        }

        private void LearningToMSEmin(string kind)
        {
            ChartForm chartFormMSE = null;
            this.Invoke(new MethodInvoker(() =>
            {
                this.info.Text = "Learning...";
                this.checkEditCharts.Enabled = false;
            }));
            float mse = 1.0f;
            float mseL = mse;
            float mseLL = mseL;
            this.Invoke(new MethodInvoker(() =>
            {
                if (true == checkEditCharts.Checked)
                {
                    chartFormMSE = new ChartForm();
                    chartFormMSE.ClearValues();
                    chartFormMSE.SetValuesName("MSE");
                    chartFormMSE.Location = new Point(parentForm.Location.X + parentForm.Size.Width, parentForm.Location.Y);
                    chartFormMSE.Height = parentForm.Height;
                    chartFormMSE.Show();
                }
            }));
            do
            {
                mseLL = mseL;
                mseL = mse;
                mse = this.Hopfield.LearnStepHN(kind);
                this.Invoke(new MethodInvoker(() =>
                {
                    this.infoMSE.Text = string.Format(CultureInfo.InvariantCulture, "MSE = {0}", mse.ToString(CultureInfo.InvariantCulture));
                    if (true == checkEditCharts.Checked)
                    {
                        chartFormMSE.AddValue(mse);
                        chartFormMSE.Refresh();
                    }
                }
                ));
            }
            while (mse < mseL || mseL < mseLL || mse < mseLL);

            this.Invoke(new MethodInvoker(() =>
            {
                this.info.Text = "Ready.";
                this.checkEditCharts.Enabled = true;
            }));
        }

        private void ThreadLearnMethod(object kind)
        {
            string k = kind.ToString();
            lock (Mutex)
            {
                this.LearningToMSEmin(k);
            }
        }

        public void SimulatorLearn()
        {
            ParameterizedThreadStart threadStart = new ParameterizedThreadStart(this.ThreadLearnMethod);
            Thread th = new Thread(threadStart);
            // 1st kind of learning - h - one step
            // 2nd kind of learning - d - recursive, the best
            // 3rd kind of learning - z - recursive, worst then above
            // 4th kind of learning - p - recursive, for predictor
            // 5th kind of learning - s - recursive, for sequences
            th.Start("d");
        }

        private void SimulateToMSEmin(object kind)
        {
            string k = kind.ToString();
            ChartForm chartFormMSE = null;
            ChartForm chartFormE = null;
            this.Invoke(new MethodInvoker(() =>
            {
                this.info.Text = "Simulating...";
                this.checkEditCharts.Enabled = false;
            }));
            Bitmap bmp = this.Hopfield.CreateBmp();
            this.Invoke(new MethodInvoker(() =>
            {
                Hopfield.RandY(this.noise);
                Hopfield.DrawTextureUsingPointers(bmp);
                mainSimulatorPictureBox.Image = bmp;
                mainSimulatorPictureBox.Refresh();
            }));
            float mse = 1.0f;
            float mseMin = mse;
            float mseMinL = mseMin;
            float energy = 0.0f;
            float energyLast = energy;
            float energyLastLast = energyLast;
            this.Invoke(new MethodInvoker(() =>
            {
                if (true == checkEditCharts.Checked)
                {
                    chartFormMSE = new ChartForm();
                    chartFormE = new ChartForm();
                    chartFormMSE.ClearValues();
                    chartFormMSE.SetValuesName("MSE");
                    chartFormMSE.Location = new Point(parentForm.Location.X + parentForm.Size.Width, parentForm.Location.Y);
                    chartFormMSE.Show();
                    chartFormE.ClearValues();
                    chartFormE.SetValuesName("E");
                    chartFormE.Location = new Point(chartFormMSE.Location.X, chartFormMSE.Location.Y + chartFormMSE.Size.Height);
                    chartFormE.Show();
                }
            }));
            Thread.Sleep(1000);
            do
            {
                mseMinL = mseMin;
                mseMin = mse;
                mse = this.Hopfield.CompHN(k);
                energyLastLast = energyLast;
                energyLast = energy;
                energy = this.Hopfield.Energy;
                this.Invoke(new MethodInvoker(() =>
                {
                    Hopfield.DrawTextureUsingPointers(bmp);
                    mainSimulatorPictureBox.Refresh();
                    this.infoMSE.Text = string.Format(CultureInfo.InvariantCulture, "MSE = {0}", mse.ToString(CultureInfo.InvariantCulture));
                    if (true == checkEditCharts.Checked)
                    {
                        chartFormMSE.AddValue(mse);
                        chartFormMSE.Refresh();
                        chartFormE.AddValue(energy);
                        chartFormE.Refresh();
                    }
                }
                ));
            }
            while ((mse < mseMin || mseMin < mseMinL || mse < mseMinL) ||
                     (energy < energyLast || energyLast < energyLastLast || energy < energyLastLast));
            this.Invoke(new MethodInvoker(() =>
            {
                this.info.Text = "Ready.";
                this.checkEditCharts.Enabled = true;
            }));
        }

        private void ThreadSimulateMethod(object kind)
        {
            string k = kind.ToString();
            lock (Mutex)
            {
                this.SimulateToMSEmin(k);
            }
        }

        public void SimulatorSimulate()
        {
            ParameterizedThreadStart threadStart = new ParameterizedThreadStart(this.ThreadSimulateMethod);
            Thread th = new Thread(threadStart);
            // 1st kind - a - synchronus - fast
            // 2nd kind - s - asynchronus - slower
            // 3rd kind - r - randomly - slowest
            th.Start("a");
        }

        private void AttractorAddFromPPM()
        {
            this.Invoke(new MethodInvoker(() =>
            {
                using (OpenFileDialog ofd = new OpenFileDialog())
                {
                    ofd.Filter = "PPM ASCII Image (*.ppm)|*.ppm";
                    ofd.Multiselect = true;
                    if (ofd.ShowDialog() != DialogResult.OK) return;

                    for (int i = 0; i < ofd.FileNames.Length; i++)
                    {
                        string fileName = ofd.FileNames[i];

                        this.Hopfield.Colorized = this.checkBoxColors.Checked;
                        this.Hopfield.OpenImgFile(fileName);
                        this.Hopfield.AddAttractor();
                        this.mainAttractorsListBox.Items.Add(this.mainAttractorsListBox.Items.Count.ToString(CultureInfo.InvariantCulture));

                    }

                    this.checkBoxColors.Enabled = this.mainAttractorsListBox.Items.Count == 0;

                    if (this.ChangedAttractorList != null)
                    {
                        this.ChangedAttractorList(this, EventArgs.Empty);
                    }
                }
            }));
        }

        private void ThreadAddAttractorMethod()
        {
            lock (Mutex)
            {
                this.AttractorAddFromPPM();
            }
        }

        public void AttractorAdd()
        {
            ThreadStart threadStart = new ThreadStart(this.ThreadAddAttractorMethod);
            Thread th = new Thread(threadStart);
            th.Start();
        }

        private void AttractorRemoveFromList()
        {
            this.Invoke(new MethodInvoker(() =>
            {
                int k = this.mainAttractorsListBox.SelectedIndex;
                if (k == -1) return;

                this.Hopfield.DelAttractor(k);

                int c = this.Hopfield.LCount;
                this.mainAttractorsListBox.Items.Clear();
                for (int i = 0; i < c; i++)
                {
                    this.mainAttractorsListBox.Items.Add(i);
                }

                this.checkBoxColors.Enabled = this.mainAttractorsListBox.Items.Count == 0;

                if (this.ChangedAttractorList != null)
                {
                    this.ChangedAttractorList(this, EventArgs.Empty);
                }
            }));
        }

        private void ThreadRemoveAttractorMethod()
        {
            lock (Mutex)
            {
                this.AttractorRemoveFromList();
            }
        }

        public void AttractorRemoveSelected()
        {
            ThreadStart threadStart = new ThreadStart(this.ThreadRemoveAttractorMethod);
            Thread th = new Thread(threadStart);
            th.Start();
        }

        private void mainAttractorsListBox_SelectedIndexChanged(object sender, EventArgs e)
        {
            int i = int.Parse(mainAttractorsListBox.SelectedItem.ToString(), CultureInfo.InvariantCulture);
            this.Hopfield.LoadKtoY(i);
            Bitmap bmp = this.Hopfield.CreateBmp();
            this.Hopfield.DrawTextureUsingPointers(bmp);
            this.mainSimulatorPictureBox.Image = bmp;
        }

        private void textBoxNoise_TextChanged(object sender, EventArgs e)
        {
            int.TryParse(textBoxNoise.Text, NumberStyles.Integer, CultureInfo.InvariantCulture, out this.noise);
        }

        private void textBoxD_TextChanged(object sender, EventArgs e)
        {
            int d = 0;
            if (true == int.TryParse(textBoxD.Text, NumberStyles.Integer, CultureInfo.InvariantCulture, out d))
            {
                this.Hopfield.Setd(d);

                if (this.ChangedD != null)
                {
                    this.ChangedD(this, EventArgs.Empty);
                }
            }
        }

        private void textBoxEta_TextChanged(object sender, EventArgs e)
        {
            float eta = 0.7f;
            if (true == float.TryParse(textBoxEta.Text, NumberStyles.Float, CultureInfo.InvariantCulture, out eta))
            {
                this.Hopfield.Seteta(eta);

                if (this.ChangedEta != null)
                {
                    this.ChangedEta(this, EventArgs.Empty);
                }
            }
        }

        #endregion

        #region Events

        public event EventHandler ChangedAttractorList;
        public event EventHandler ChangedD;
        public event EventHandler ChangedEta;

        #endregion
    }
}
